package ipmss.planning;

import ipmss.planning.commands.BackCommand;
import ipmss.planning.commands.CommandResult;
import ipmss.planning.commands.ScheduleCommand;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Named;

// TODO: Auto-generated Javadoc
/**
 * The Class ScheduleInvokerBean.
 *
 * @author Michał Czarnik
 */
@Named
public class ScheduleInvokerBean implements ScheduleInvoker
{
    
    /** The thread pool size. */
    private static int threadPoolSize = 30;
    
    /** The project queue max size. */
    private static int projectQueueMaxSize = 300;
    
    /** The project count. */
    private static long projectCount;
    
    /** The Constant logger. */
    private static final Logger logger = Logger.getLogger(ScheduleInvokerBean.class.getName());

    /** The queue. */
    BlockingDeque<ScheduleCommand> queue = new LinkedBlockingDeque<ScheduleCommand>();

    /** The project queues. */
    ConcurrentHashMap<Integer, BlockingDeque<ScheduleCommand>> projectQueues = new ConcurrentHashMap<Integer, BlockingDeque<ScheduleCommand>>();

    // ExecutorService executor = Executors.newSingleThreadExecutor();
    /** The executors. */
    ConcurrentHashMap<Integer, ExecutorService> executors = new ConcurrentHashMap<Integer, ExecutorService>(threadPoolSize);

    /* (non-Javadoc)
     * @see ipmss.planning.ScheduleInvoker#execute(ipmss.planning.commands.ScheduleCommand)
     */
    public CommandResult execute(ScheduleCommand command)
    {
	logger.log(Level.INFO, "START|ProjectId:" + command.getProjectId() + " Invoke Command: " + command.getClass().getName());
	ExecutorService executorService = executors.get(new Integer(command.getProjectId()));
	if (executorService == null)
	{
	    executorService = addExecutorForNewProject(command.getProjectId());
	}
	BlockingDeque<ScheduleCommand> projectQueue = projectQueues.get(command.getProjectId());
	if (projectQueue == null)
	{
	    projectQueue = projectQueues.putIfAbsent(command.getProjectId(), new LinkedBlockingDeque<ScheduleCommand>());
	}
	CommandResult result = invokeCommand(command, executorService, projectQueue); 
	logger.log(Level.INFO, "END|ProjectId:" + command.getProjectId() + " Invoke Command: " + command.getClass().getName());
	return result;
    }

    /**
     * Invoke command.
     *
     * @param command the command
     * @param executorService the executor service
     * @param projectQueue the project queue
     * @return the command result
     */
    protected CommandResult invokeCommand(ScheduleCommand command, ExecutorService executorService,
	    BlockingDeque<ScheduleCommand> projectQueue)
    {
	CommandResult result = null;
	try
	{
	    result = executorService.submit(command).get();
	} catch (InterruptedException e)
	{
	    logger.log(Level.SEVERE, "Command invoke error\n" + e.toString());

	} catch (ExecutionException e)
	{
	    logger.log(Level.SEVERE, "Command invoke error\n" + e.toString());
	}
	projectQueue.addFirst(command);
	if (projectQueue.size() > projectQueueMaxSize)
	    projectQueue.pollLast();
	return result;
    }

    /**
     * Invoke command.
     *
     * @param command the command
     * @param executorService the executor service
     * @param projectQueue the project queue
     * @return the command result
     */
    protected CommandResult invokeCommand(BackCommand command, ExecutorService executorService, BlockingDeque<ScheduleCommand> projectQueue)
    {
	ScheduleCommand cmd = projectQueue.pollFirst();
	
	ArrayList<ScheduleCommand> backCommands = new ArrayList<ScheduleCommand>();
	while(cmd instanceof BackCommand)
	{
	    backCommands.add(cmd);
	    cmd = projectQueue.pollFirst();
	}
	for(int i = backCommands.size() -1 ; i >= 0 ;i--)
	{
	    try
	    {
		projectQueue.putFirst(backCommands.remove(i));
	    } catch (InterruptedException e)
	    {
		logger.log(Level.SEVERE, "Command invoke error\n" + e.toString());
	    }
	}
	
	if (cmd != null)
	{
	    command.setUndoCommand(projectQueue.pollFirst());

	    CommandResult result = null;
	    try
	    {
		result = executorService.submit(command).get();
	    } catch (InterruptedException e)
	    {
		logger.log(Level.SEVERE, "Command invoke error\n" + e.toString());

	    } catch (ExecutionException e)
	    {
		logger.log(Level.SEVERE, "Command invoke error\n" + e.toString());
	    }
	    if (result.getStatus().equals(CommandResult.Status.SUCCES))
		projectQueue.addFirst(command);
	    if (projectQueue.size() > projectQueueMaxSize)
		projectQueue.pollLast();
	    return result;
	} else
	{
	    CommandResult result = new CommandResult();
	    result.setStatus(CommandResult.Status.NO_BACK);
	    return result;
	}

    }

    /**
     * Adds the executor for new project.
     *
     * @param projectId the project id
     * @return the executor service
     */
    private synchronized ExecutorService addExecutorForNewProject(int projectId)
    {
	ExecutorService executorService = executors.get(new Integer(projectId));
	if (executorService == null)
	{
	    if (projectCount < threadPoolSize)
	    {
		executorService = Executors.newSingleThreadExecutor();
	    } else
	    {
		Enumeration<ExecutorService> en = executors.elements();
		for (int i = 0; i < projectCount % threadPoolSize; i++)
		    en.nextElement();
		executorService = en.nextElement();
	    }
	    executors.put(projectId, executorService);
	    projectCount++;
	}
	return executorService;
    }

}
